#include <PalindromeChecker.hpp>
#include <WordDataReader.hpp>
#include <PhraseDataReader.hpp>
#include <iostream>
#include <mpi.h>
#include <ctime>

const int maxPrimeNumber = 10000;

int main (int argc, char *argv[]) {


	// Inicia o ambiente paralelo
	MPI::Init(argc, argv);

	// Obtem o proprio ID
	int id = MPI::COMM_WORLD.Get_rank();

	// Verifica se a entrada esta correta
	if(argc != 3){
		if (!id)
			std::cout << argv[0] << " TIPO_ENTRADA ENTRADA.TXT\n"
					"Tipos possiveis:\n"
					"0: texto curto\n"
					"1: texto longo\n";
		return 1;
	}

	// Obtem quantos processos existem
	int processes = MPI::COMM_WORLD.Get_size();
	int processesM1 = processes-1;
	char buffer[4096];
	MPI::Status status;

	// Numero de processos executando cada etapa
	int maxNumThreadsEratostenes = processes;
	int maxNumThreadsPalindrome  = processes;

	//  buffer para troca de mensagens
	int i10msg[2];

	// Para troca de mensagens (especificamente qtde de instruncoes)
	long long llmsg;


	// Leitura do arquivo de entrada. TODOS os processos leem o arquivo.
	std::vector<std::pair<std::string, unsigned> >* v;
	{	// READING INPUT
		DataReader * dR;
	
		if(argv[1][0] == '0')
			dR = new PhraseDataReader(argv[2]);
		else
			dR = new WordDataReader(argv[2]);
		
		if (!id)
			std::cout << "Reading input file. Wait.\n";
		v = dR->readData();
	
	}

	if (id == 0) {

		// Aguarda todos os processos lerem os arquivos
		MPI::COMM_WORLD.Barrier();
		if (!id)
			std::cout << "Input file read successfuly\n";
		

		// Cria contadores de instrucoes para cada processo
		long long *icountEra = new long long [maxNumThreadsEratostenes];
		long long *icountPalin = new long long [maxNumThreadsPalindrome];


		// Zera contador de instrucoes
		for(int i = 0; i < maxNumThreadsEratostenes; i++) icountEra[i] = 0L;
		for(int i = 0; i < maxNumThreadsPalindrome; i++) icountPalin[i] = 0L;

		std::cout << "Generating primes until " << maxPrimeNumber 
		<< std::endl;

		double timeEratostenes, timePalindrome;


		// // // // // // // // // // // // 
		// ERATOSTENES
		// // // // // // // // // // // // 

		// Obtem tempo inicial
		timeEratostenes = MPI::Wtime();


		// Crivo de eratostenes
		bool * sieve = new bool[maxPrimeNumber + 1];
		{
			// Vetor que indica que os processos ja' terminaram suas funcoes
			bool * finished = new bool[processes];

			// Todos os pares sao nao-primos...
			for (int i = 1; i <= maxPrimeNumber; i++)
				sieve[i] = i % 2 == 1;
			// ... com excecao do 2
			sieve[2] = true;
			// 1 nao e' primo!
			sieve[1] = false;

			// Navega pelos impares
			for (int i = 3; i * i <= maxPrimeNumber; i += 2, *icountEra += 2L)
				// Caso seja um impar confirmado primo
				if (sieve[i])
				{
					// Prepara mensagem indicando qual primo e' esse
					i10msg[0] = i;
					*icountEra += 1L;
					// Avisa todos os processos do primo encontrado, para que calculem seus multiplos
					for (int j = 1; j < processes; j++, *icountEra += 4L) {
						MPI::COMM_WORLD.Send(i10msg, 1, MPI_INT, j, 1);
						finished[j] = false;
					}
					// Aguarda todos os processos terminarem
					for (bool ok = false; !ok; *icountEra += 1) {
						ok = true;
						*icountEra += 1;
						for (int j = 1; j < processes; j++) {
							*icountEra += 1;
							// Caso o j-esimo processo nao tenha terminado
							if (!finished[j]) {
								*icountEra += 5;
								ok = false;
								// Recebe uma mensagem
								for (int st = 0; st < 1; ) {
									MPI::COMM_WORLD.Recv(i10msg, 1, MPI_INT, j, 1, status);
									st += status.Get_count(MPI_INT);
								}
								// Se for uma mensagem de indicacao de fim
								if (i10msg[0] == -1) {
									// Termina
									finished[j] = true;
								} else {
									// Marca o numero da mensagem recebida como nao primo
									sieve[i10msg[0]] = false;
								}
							}
						}
					}
				}
			delete [] finished;
			i10msg[0] = -1;

			// Envia mensagens indicando fim do calculo do crivo de Eratostenes
			for (int j = 1; j < processes; j++) {
				MPI::COMM_WORLD.Send(i10msg, 1, MPI_INT, j, 1);
				finished[j] = false;
				for (int st = 0; st < 1; ) {
					MPI::COMM_WORLD.Recv(icountEra + j, 1, MPI_LONG_LONG_INT, j, 1, status);
					st += status.Get_count(MPI_LONG_LONG_INT);
				}
			}
		}

		// obtem variacao do tempo desde o tempo inical
		timeEratostenes = MPI::Wtime() - timeEratostenes;

		// MPI::COMM_WORLD.Barrier();

		// std::cout << "Reading input file. Wait.\n";

		// DataReader * dR;

		// if(argv[1][0] == '0')
		// 	dR = new PhraseDataReader(argv[2]);
		// else
		// 	dR = new WordDataReader(argv[2]);


		// std::vector<std::pair<std::string, unsigned> >* v;
		
		// v = dR->readData();

		// std::cout << "Input file read successfuly\n";

		int * palcheck;
		bool * pricheck;
		int size = v->size();
		palcheck = new int[size];
		for (int i = 0; i < size; i++) {
			palcheck[i] = 0;
		}
		pricheck = new bool[size];
		int sum;



		// MPI::COMM_WORLD.Barrier();

		timePalindrome = MPI::Wtime();


		// CODIGO ANTIGO ABAIXO! ENVIAVA AS PALAVRAS
		// (Antes, as palavras eram enviadas com send para os processos)

		// std::cout << "Sending words and theirs indexes\n";



		// for (int i = 0, _proc = 0; i < size;
		//  	i++, _proc = (_proc + 1) % processesM1)  {
		// 	int proc = _proc+1;

		// 	i10msg[0] = i;
		// 	i10msg[1] = (*v)[i].first.length();

		// 	MPI::COMM_WORLD.Send(i10msg, 2, MPI_INT, proc, 1);

		// 	MPI::COMM_WORLD.Send((*v)[i].first.c_str(), i10msg[1], MPI_CHAR, proc, 1);
		// }

		// for (int i = 1; i < processes; i++) {
		// 	i10msg[0] = -1;
		// 	i10msg[1] = -1;
		// 	MPI::COMM_WORLD.Send(i10msg, 2, MPI_INT, i, 1);
		// }

		// Aguarda todos os processos estarem prontos para comecar a etapa
		// de verificacao de palindromos
		MPI::COMM_WORLD.Barrier();

		std::cout << "Veryfing the text\n";

		// Vetor que indica se todos os processos terminaram seus servicos
		bool * finished = new bool[processes];
		for (int i = 0; i < processes; i++) finished[i] = false;
		finished[0] = true;

		// Aguarda processos terminarem
		for (bool ok = false; !ok;) {
			ok = true;

			for (int i = 1; i < processes; i++) {
				// Caso o i-esimo processo nao tenha terminado
				if (!finished[i]) {
					ok = false;
					// Recebe uma mensagem do i-esimo processo
					for (int st = 0; st < 2; ) {
						MPI::COMM_WORLD.Recv(i10msg, 2, MPI_INT, i, 1, status);
						st += status.Get_count(MPI_INT);
					}
					// Caso seja uma mensagem de fim de execucao
					if (i10msg[0] == -1) {
						finished[i] = true;
						// Recebe quantidade de instrucoes executadas por aquele processo
						for (int st = 0; st < 1; ) {
							MPI::COMM_WORLD.Recv(&llmsg, 1, MPI_LONG_LONG_INT, i, 1, status);
							st += status.Get_count(MPI_LONG_LONG_INT);
						}
						// Atribuit tal quantidade
						icountPalin[i] += llmsg;
					} else {
						// Caso contrario, o processo enviou um indice de uma palavra
						// que e' palindromo
						palcheck[i10msg[1]] = i10msg[0];
						pricheck[i10msg[1]] = sieve[i10msg[0]];
					}
				}
			}
		}
		delete [] finished;
		timePalindrome = MPI::Wtime() - timePalindrome;
	
		std::cout << "\nResults\n";

		// Imprime resultados
		for (int i = 0; i < size; i++)
			if (sum = palcheck[i])
				std::cout <<
				"(" << (*v)[i].second << "x) " <<
				(*v)[i].first << " : sum " << sum << " is" <<
				(pricheck[i]?" ":" NOT ") << "prime.\n";


		std::cout << std::endl;

		//imprimindo contadores
		for(int i = 0; i< maxNumThreadsEratostenes; i++)
			std::cout << "Eratostenes's [" << i << "] instruction count: " << icountEra[i] << "\n";

		std::cout << std::endl;

		for(int i = 0; i< maxNumThreadsPalindrome; i++)
			std::cout << "Palindrome check's [" << i << "] instruction count: " << icountPalin[i] << "\n";

		std::cout << std::endl;

		std::cout << "Eratostenes's required time: " << timeEratostenes << "s\n";
		std::cout << "Palindrome check's required time: " << timePalindrome << "s\n";

		delete [] icountEra;
		delete [] icountPalin;
		delete [] palcheck;
		delete [] pricheck;
		delete v;
		delete sieve;

	}



	 else
	 	/////////////////////////////////////////////
	 	////////////////////////////////////////////
	 	/// ESCRAVOS //////////////////////////////
	 	//////////////////////////////////////////
	 	/////////////////////////////////////////
	 {
	 	// Agurada todos os processos lerem o arquivo
	 	MPI::COMM_WORLD.Barrier();

		// Eratostenes
	 	{
			long long icnt(0L);
			// Enquanto o mestre nao mandar parar
			for(bool ok = false; !ok; ) {
				icnt += 2;
				// Recebe ordem do mestre
				for (int st = 0; st < 1;) {
					MPI::COMM_WORLD.Recv(i10msg, 1, MPI_INT, 0, 1, status);
					st += status.Get_count(MPI_INT);
				}
				// Caso seja ordem para parar
				if (i10msg[0] == -1) {
					ok = true;
					MPI::COMM_WORLD.Send(&icnt, 1, MPI_LONG_LONG_INT, 0, 1);
				} else {
					// Caso contrario, mestre mandou um primo confirmado
					int i = i10msg[0];

					// Percorre o vetor pelos multiplos impares daquele primo, de forma intercalada
					// (pulando valores percorridos por outros processos) e envia os valores
					// antingidos para o mestre
					for (int j = i * i + ((id-1) * (i<<1)), k = (processes-1) * (i<<1); j <= maxPrimeNumber; j += k, icnt+= 4) {
							i10msg[0] = j;
							MPI::COMM_WORLD.Send(i10msg, 1, MPI_INT, 0, 1);
					}
					icnt += 2;
					i10msg[0] = -1;
					// Envia fim do servico
					MPI::COMM_WORLD.Send(i10msg, 1, MPI_INT, 0, 1);
				}
			}
		}

		// // // // // // // // // // 
		// Palidrome Checking
		// // // // // // // // // // 

		std::vector<std::pair<std::string, int> > _v;

		// Adiciona ao vetor _v apenas as palavras que ESSE processo executara
		// (divisao intercalada)

		for (int i = id - 1; i < v->size(); i += processesM1) {
			_v.push_back(std::pair<std::string, int> ((*v)[i].first, i));
		}


		// ABAIXO ESTA O CODIGO ANTIGO, QUANDO RECEBIA PALAVRAS
		// DO SERVIDOR!

		// for(;;) {
		// 	int length;
		// 	int index;
		// 	for (int st = 0; st < 2;) {
		// 		MPI::COMM_WORLD.Recv(i10msg, 2, MPI_INT, 0, 1, status);
		// 		st += status.Get_count(MPI_INT);
		// 	}
		// 	index = i10msg[0];
		// 	length = i10msg[1];

		// 	if (index == -1) {
		// 		break;
		// 	}

		// 	for (int st = 0; st < length;) {
		// 		MPI::COMM_WORLD.Recv(buffer, length, MPI_CHAR, 0, 1, status);
		// 		st += status.Get_count(MPI_CHAR);
		// 	}
		// 	buffer[length] = '\0';

		// 	std::string str(buffer);
		// 	v.push_back(std::pair<std::string, int> (str, index));
		// }


		long long total (0);

		// Aguarda sincronizacao para todos comecarem a checagem de palindromos
		MPI::COMM_WORLD.Barrier();

		PalindromeChecker * pC  = new PalindromeChecker();

		// Checa os palindromos
		for (int i = 0; i < _v.size(); i++) {
			int palcheck = pC->check(_v[i].first, &total);
			
			// Caso seja um palindromo
			if (palcheck) {
				// Primeiro parametro da mensagem = soma dos caracteres ASCII
				i10msg[0] = palcheck;
				// Segundo parametro da mensagem = indice da palavra
				i10msg[1] = _v[i].second;
				// Envia mensagem para o mestre
				MPI::COMM_WORLD.Send(i10msg, 2, MPI_INT, 0, 1);
			}

		}

		// Envia sinal de fim de servico

		i10msg[0] = -1;
		i10msg[1] = id;

		MPI::COMM_WORLD.Send(i10msg, 2, MPI_INT, 0, 1);

		// Envia quantas instrucoes executou

		MPI::COMM_WORLD.Send(&total, 1, MPI_LONG_LONG_INT, 0, 1);

		std::cout << "Process " << id << " ended\n";

		delete pC;
	}



	MPI::Finalize();
	return 0;
}
